<?php

Raise::load('core.RaiseObject');
Raise::load('RaiseLocale');
Raise::load('core.io.file.RaiseFile');
Raise::load('core.io.file.RaisePath');
Raise::load('core.RaiseString');
Raise::load('core.text.regex.RaiseRegex');

class RaiseTranslator extends RaiseObject {
    
    /**
     * The package which the translation will be loaded from
     * @var string
     */
    private $package;
    
    /**
     * The original language to be translated from
     * @var string
     */
    private $originalLanguage;
    
    /**
     * The targeted language to translate to
     * @var string
     */
    private $targetLanguage;
    
    /**
     * The language file containing the original messages
     * @var RaiseFile
     */
    private $originalFile;
    
    /**
     * The language file containing the targeted language messages
     * @var RaiseFile
     */
    private $targetFile;
    
    /**
     * Create a RaiseTranslator object
     * @param string $root The path to the application folder.
     * @param string|RaiseLocale $original The original language to translate from
     * @param string|RaiseLocale $target The target language to translate to
     * @param string $package (optional) The message package
     */
    public function __construct($root, $original, $target, $package = null){
        $this->package = $package;
        if($original instanceof RaiseLocale){
            $original = $original->__toString();
        }
        $this->originalLanguage = $original;
        if($target instanceof RaiseLocale){
            $target = $target->__toString();
        }
        $this->targetLanguage = $target;
        $this->load($root);
    }
    
    /**
     * Get or set the original language to translate from
     * @param string $o (optional) Set the original language to translate from using a ISO 639-1 language code
     * @return string The ISO 639-1 langauge code of the original language 
     */
    public function originalLanguage($o = null){
        if(func_num_args() == 1){
            $this->originalLanguage = $o;
        }
        return $this->originalLanguage;
    }

    /**
     * Get or set the target language to translate from
     * @param string $t (optional) Set the traget language to translate to using a ISO 639-1 language code
     * @return string The ISO 639-1 langauge code of the target language 
     */
    public function targetLanguage($t = null){
        if(func_num_args() == 1){
            $this->targetLanguage = $t;
        }
        return $this->targetLanguage;
    }
    
    /**
     * Prepare and load the files
     * @param string $root The path to the application folder.
     */
    private function load($root = ''){
        if($this->package == null){
            $this->package = '';
        }
        $path = RaisePath::combine($root, 'raise', 'language', $this->package, $this->originalLanguage . '.t');
        $this->originalFile = new RaiseFile($path);
        if(!$this->originalFile->exists() && strpos($this->originalLanguage, '-') !== false){
            $l = current(explode('-',$this->originalLanguage));
            $path = RaisePath::combine($root, 'raise', 'language', $this->package, $l . '.t');
            $this->originalFile = new RaiseFile($path);
        }
        $path = RaisePath::combine($root, 'raise', 'language', $this->package, $this->targetLanguage . '.t');
        $this->targetFile = new RaiseFile($path);
        if(!$this->targetFile->exists() && strpos($this->targetLanguage, '-') !== false){
            $l = current(explode('-',$this->targetLanguage));
            $path = RaisePath::combine($root, 'raise', 'language', $this->package, $l . '.t');
            $this->targetFile = new RaiseFile($path);
        }
    }
    
    /**
     * Translate the message
     * @param string $msg The message to translate
     * @param array|RaiseCollection $params (optional) Parameters to the message
     * @return string The translated message. If the string is not found in the
     *      original translation, or the translation is not found in the target 
     *      language, the original message will be returned instead.
     */
    public function t($msg, $params = array()){
        if($this->originalLanguage != $this->targetLanguage){
            $key = $this->findKey($this->originalFile, $msg);
            if($key === null){
                return $msg;
            }
            $message = $this->findMessage($this->targetFile, $key);
        }else{
            $message = $msg;
        }
        $message = $this->parseMessage($message, $params);
        return $message;
    }
    
    /**
     * Translate the message with a quantifer
     * @param string $msg The message to translate
     * @param integer|double $quantifier The quantifier to determine which line to use
     * @param array|RaiseCollection $params (optional) Parameters to the message
     * @return string The translated message. If the string is not found in the
     *      original translation, or the translation is not found in the target 
     *      language, the original message will be returned instead.
     */
    public function n($msg, $quantifier, $params = array()){
        $key = $this->findKey($this->originalFile, $msg);
        if($key === null){
            return $msg;
        }
        $key = new RaiseString($key);
        if($key->lastIndexOf('#') >= 0){
            $key = $key->substring(0, $key->lastIndexOf('#'));
        }
        
        $equations = $this->findMessageEquations($this->targetFile, $key);
        foreach($equations as $equation){
            if($this->doEquationMatching($equation, $quantifier)){
                $message = $this->findMessage($this->targetFile, $key . '#' . $e);
                $message = $this->parseMessage($message, $params);
                return $message;
            }
        }
        return $msg;
    }
    
    /**
     * Parse and prepare a message
     * @param string $message The string to parse
     * @param array|RaiseCollection $fields The parameters
     * @return string
     */
    private function parseMessage($message, $fields){
        $template = new RaiseTemplate($message);
        foreach($fields as $k => $p){
            $template->fields()->add($k, $p);
        }
        $message = $template->parse();
        return $message;
    }
    
    /**
     * Check if a equation matches the quantifier
     * @param string $equation The equation to balance
     * @param integer|double $quantifier The quantifier
     * @return boolean
     */
    private function doEquationMatching($equation, $quantifier){
        $regex = new RaiseRegex('`^([0-9]*|[n]{1})(\<|\>|\=|\=\=|\!\=|\<\=|\>\=)([0-9]+|[n]{1})$`is');
        $matches = $regex->match($equation);
        $match = false;
        if($matches->count() == 4){
            $op = $matches->get(2)->match();
            $left = $matches->get(1)->match();
            if($left === '' || $left == 'n'){
                $left = $quantifier;
            }
            $right = $matches->get(3)->match();
            if($right == 'n'){
                $right = $quantifier;
            }
            switch($op){
                case '=':
                case '==':
                    $match = ($left == $right);
                    break;
                case '!=':
                    $match = ($left != $right);
                    break;
                case '>':
                    $match = ($left > $right);
                    break;
                case '<':
                    $match = ($left < $right);
                    break;
                case '>=':
                    $match = ($left >= $right);
                    break;
                case '<=':
                    $match = ($left <= $right);
                    break;
            }
        }
        return $match;
    }
    
    /**
     * Encode a message
     * @param string $msg The original message
     * @return string The encoded message
     */
    private function encode($msg){
        $msg = str_replace(array('\\', '#', '[', ']'), array('\\\\', '\\#', '\\[', '\\]'), $msg);
        return $msg;
    }
    
    /**
     * Decode a message
     * @param string $msg The encoded message
     * @return string The decoded message
     */
    private function decode($msg){
        $msg = str_replace(array('\\\\', '\\#', '\\[', '\\]'), array('\\', '#', '[', ']'), $msg);
        return $msg;
    }
    
    /**
     * Find all quantifer equations using the key from a file
     * @param RaiseFile $file The file to find quantifier equations
     * @param string $key The key of the message
     * @return RaiseCollection A collection of all the available quantifier equations options for the key
     */
    private function findMessageEquations($file, $key){
        if(!$file->exists()){
            throw new RaiseFileNotFoundException('RaiseTranslator::findMessage() cannot find the translation file \'' . $file->pathname() . '\'.');
        }
        $equations = new RaiseCollection();
        $stream = $file->stream();
        $length = $stream->length();
        $cPos = 0;
        $keyLength = strlen($key);
        while($stream->position() < $length){
            $c = $stream->read(1);
            if($c == substr($key, $cPos, 1)){
                $cPos++;
            }else{
                $cPos = 0;
            }
            if($cPos == $keyLength){
                // found key
                $c = $stream->read(1);
                if($c == '#'){
                    $equationStart = $stream->position();
                    while($stream->position() < $length){
                        $x = $stream->read(1);
                        if($x == '['){
                            break;
                        }
                    }
                    $equationEnd = $stream->position() - 1;
                    $equationLength = $equationEnd - $equationStart;
                    $stream->seek($equationStart);
                    $equation = $stream->read($equationLength);
                    if(!$equations->valueExists($equation)){
                        $equations->add($equation);
                    }
                }
                $cPos = 0;
            }
        }
        return $equations;
    }
    
    /**
     * Find a message using the key from a file
     * @param RaiseFile $file The file to find the message
     * @param string $key The key of the message
     * @return string The message
     */
    private function findMessage($file, $key){
        if(!$file->exists()){
            throw new RaiseFileNotFoundException('RaiseTranslator::findMessage() cannot find the translation file \'' . $file->pathname() . '\'.');
        }
        $stream = $file->stream();
        $length = $stream->length();
        $cPos = 0;
        $keyLength = strlen($key);
        while($stream->position() < $length){
            $c = $stream->read(1);
            if($c == substr($key, $cPos, 1)){
                $cPos++;
            }else{
                $cPos = 0;
            }
            if($cPos == $keyLength){
                // found message
                while($stream->position() < $length){
                    $x = $stream->read(1);
                    if($x == '['){
                        break;
                    }
                }
                $msgStart = $stream->position();
                while($stream->position() < $length){
                    $x = $stream->read(1);
                    if($x == ']'){
                        break;
                    }
                }
                $msgEnd = $stream->position();
                $msgLength = $msgEnd - $msgStart;
                $stream->seek($msgStart);
                return $this->decode($stream->read($msgLength - 1));
            }
        }
        return null;
    }
    
    /**
     * Find the key of a message from the file
     * @param RaiseFile $file The file to look for the key
     * @param string $msg The message whose key is to be found
     * @return string The key of the message
     */
    private function findKey($file, $msg){
        if(!$file->exists()){
            throw new RaiseFileNotFoundException('RaiseTranslator::findKey() cannot find the translation file \'' . $file->pathname() . '\'.');
        }
        $msg = '[' . $this->encode($msg) . ']';
        $stream = $file->stream();
        $length = $stream->length();
        $cPos = 0;
        $msgLength = strlen($msg);
        $lastBreak = 0;
        while($stream->position() < $length){
            $c = $stream->read(1);
            if($c == substr($msg, $cPos, 1)){
                $cPos++;
            }else{
                $cPos = 0;
            }
            if($cPos == $msgLength){
                // found message
                $keyEnd = $stream->position() - $msgLength;
                $keyLength = $keyEnd - $lastBreak;
                $stream->seek($lastBreak);
                return trim($stream->read($keyLength));
            }
            if($c == ']'){
                $lastBreak = $stream->position() + 1;
            }
        }
        return null;
    }
    
}